import org.newdawn.slick.*;
import org.newdawn.slick.util.*;
import org.newdawn.slick.gui.*;

import java.util.*;

public class ChatLobbyState implements ApplicationState, ComponentListener {

	public static final int HOST = 0;
	public static final int JOIN = 1;
	public static final int REMATCH = 2;

	private static final int RIGHTCOL_X = 670;
	private static final int RIGHTCOL_SIZE = 32;
	private static final int RIGHTCOL_DOWN_Y = 328;
	private static final int RIGHTCOL_UP_Y = 286;
	private static final int RIGHTCOL_REFRESH_Y = 50;

	private int mouseX;
	private int mouseY;

	private int focused;
	private int selected;

	private int scroll;

	private ApplicationStateManager stateMan;

	private TextField chatBox;

	private Checkbox joinAsObserver;

	private GameContainer container;

	private int mode;

	private MenuButton disconnect;
	private MenuButton start;

	private Image[][] lobbyGfx;

	private boolean shift;

	//private ArrayList<String> chatLog = new ArrayList<String>();
	// Tuple2<PlayerId, Chat message>
	private ArrayList<Tuple3<String, Color, String>> chatLog = new ArrayList<Tuple3<String, Color, String>>();

	private ArrayList<Tuple2<Integer, String[]>> missedActions = new ArrayList<Tuple2<Integer, String[]>>();
	private StringBuilder currentState = null;
	private int wholeStateLength = 0;

	private String gameServerAddress = null;
	private String localName = null;
	private String localGameName = null;
	private boolean isConnected = false;
	private boolean isConnecting = false;
	private int connectingTo = 0; // 0 = nothing, 1 = global address, 2 = local address

	private String[] serverInfo;

	//private HashMap<Integer, String> playerNames = new HashMap<Integer, String>();
	//private HashMap<Integer, Color> playerColors = new HashMap<Integer, Color>();
	private HashMap<Integer, Client> clientList = new HashMap<Integer, Client>();
	private ArrayList<Bot> botList = new ArrayList<Bot>();


	private int lastPing = 0; // when the last ping was sent (to ensure that no timeout happens)
	private int lastUpdate = 0;

	private long ping; // the delay to the game server

	/**
	* Constructor for ChatLobbyState.
	*
	* @param manager The ApplicationStateManager controlling this instance.
	*/
	public ChatLobbyState(ApplicationStateManager manager, GameContainer cont) {
		stateMan = manager;
		container = cont;

		chatBox = new LobbyTextField("Message", container, Multitris.FONT1, 100-30, 380, 530, 24, this);
		chatBox.addListener(this);

		lobbyGfx = Multitris.lobbyHost;

		joinAsObserver = new Checkbox("Observe?", 575, 415, Multitris.FONT1, lobbyGfx[Multitris.LOBBY_HOST_PRIVATE][0], lobbyGfx[Multitris.LOBBY_HOST_PRIVATE][1], lobbyGfx[Multitris.LOBBY_HOST_PRIVATE][2], lobbyGfx[Multitris.LOBBY_HOST_PRIVATE][3], false, this);

		lobbyGfx = Multitris.chat;

		start = new MenuButton(400, 480,
			lobbyGfx[Multitris.CHAT_HOST_START][1],
			lobbyGfx[Multitris.CHAT_HOST_START][0],
			this,
			Multitris.CHAT_HOST_START);

		disconnect = new MenuButton(400, 550,
			lobbyGfx[Multitris.CHAT_HOST_LEAVE][1],
			lobbyGfx[Multitris.CHAT_HOST_LEAVE][0],
			this,
			Multitris.CHAT_HOST_LEAVE);
	}


	/**
	* Informs the LobbyState that the mouse has been moved.
	*
	* @param oldx The old x coordinate.
	* @param oldy The old y coordinate.
	* @param newx The new x coordinate.
	* @param newy The new y coordinate.
	*/
	public void mouseMoved(int oldx, int oldy, int newx, int newy) {
		mouseX = newx;
		mouseY = newy;

		if (disconnect.mouseHover(mouseX, mouseY)) {
			focused = Multitris.CHAT_HOST_LEAVE;
		} else if (mode == HOST && start.mouseHover(mouseX, mouseY)) {
			focused = Multitris.CHAT_HOST_START;
		} else if (focused != Multitris.CHAT_HOST_CHATBOX) {
			focused = -1;
		}

	}

	/**
	* Informs the LobbyState that a mouse button has been pressed.
	*
	* @param button The old mouse button pressed.
	* @param y The y coordinate.
	* @param x The x oordinate.
	*/
	public void mousePressed(int button, int x, int y) {
		mouseX = x;
		mouseY = y;

		chatBox.mousePressed(button, x, y);
		joinAsObserver.mousePressed(button, x, y);

		if (focused != Multitris.CHAT_HOST_CHATBOX ) {//&& focused != Multitris.CHAT_HOST_JOINASOBSERVER) {
			activateFocused();
		}

	}

	/**
	* Renders the LobbyState.
	*
	* @param g The graphics context to render to.
	*/
	public void render(Graphics g) {

		/*g.setColor(new Color(190,190,190));
		g.drawRect(100,50,530,310);
		g.drawRect(640,50,100,310);*/

		g.setColor(new Color(0,10,30));
		g.fillRoundRect(100-30,60,530,310,6);
		g.fillRoundRect(640-30,60,120,310,6);
		g.setColor(new Color(50,100,200));
		g.setAntiAlias(true);
		g.drawRoundRect(100-30,60,530,310,6);
		g.drawRoundRect(640-30,60,120,310,6);
		g.setAntiAlias(false);

		joinAsObserver.render(g,focused == Multitris.CHAT_HOST_JOINASOBSERVER);

		g.setColor(new Color(190,190,190));
		g.setFont(Multitris.FONT2);
		for(int i=chatLog.size()-1, j=0; i>=0 && j<15; i--, j++) {
			if (i+scroll < chatLog.size() && i+scroll >= 0) {
				Tuple3<String, Color, String> t = chatLog.get(i);
				if (t.second == null) {
					g.setColor(new Color(190,190,190));
					g.drawString("System: " + t.third, 110-30, 347-20*j);
				} else {
					int xOff = 0;
					/*g.setColor(new Color(190,190,190));
					g.drawString("<", 110+xOff, 330-20*j);
					xOff += Multitris.FONT2.getWidth("<");*/
					g.setColor(t.second);
					g.drawString(t.first, 110-30+xOff, 347-20*j);
					xOff += Multitris.FONT2.getWidth(t.first);
					g.setColor(new Color(190,190,190));
					g.drawString(": " + t.third, 110-30+xOff, 347-20*j);
				}
			}
		}

		//Set<Map.Entry<Integer, String>> names = playerNames.entrySet();
		Set<Map.Entry<Integer, Client>> clients = clientList.entrySet();
		int y = 70;
		for (Map.Entry<Integer, Client> entry : clients) {
			Client c = entry.getValue();
			//String obsString = (c.isObserving() ? "*" : "");
			if (c.isObserving()) {

				/*
				g.setAntiAlias(true);
				g.setColor(new Color(255,255,255));
				g.fillOval(650-30-2,y+4,14,7);
				g.setColor(new Color(0,0,0));
				g.fillOval(650-30+3,y+5,4,4);
				//g.setColor(new Color(150,150,150));
				g.drawOval(650-30-2,y+4,14,7);
				g.setColor(new Color(255,255,255));
				g.fillOval(650-30+3,y+5,1,1);
				g.setAntiAlias(false);
				*/

				g.setAntiAlias(true);
				//g.setColor(new Color(255,255,255));
				//g.fillOval(650-30-2,y+4,16,7);
				g.setColor(new Color(255,255,255));
				g.drawOval(650-30-2,y+4,15,7);
				//g.setColor(new Color(0,0,0));
				g.drawOval(650-30+3,y+5,5,5);
				//g.setColor(new Color(255,255,255));
				//g.fillOval(650-30+4,y+5,2,2);
				//g.setColor(new Color(0,0,0));
				//g.fillOval(650-30+4,y+5,4,4);
				g.setAntiAlias(false);

				g.setColor(c.getColor());
				g.drawString(c.getName(), 668-30, y);
				//drawDashedLine(g,620-2, y+15, 4+Multitris.FONT2.getWidth(c.getName()));//, y+15);
			} else {
				g.setColor(c.getColor());
				g.drawString(c.getName(), 650-30, y);
			}
			y += 20;
		}

		g.setColor(new Color(190,190,190));
		chatBox.render(container, g);
		
		if (wholeStateLength > 0) {
			// Observer state is downloading
			float percent = currentState.length() / (float)wholeStateLength;

			g.setColor(new Color(190,190,190));
			g.drawString("Download progress:", 100-30, 415);

			g.setColor(new Color(0,10,30));
			g.fillRoundRect(224, 415, 330, 20,6);
			g.setColor(new Color(50,100,200));
			g.setAntiAlias(true);
			g.drawRoundRect(224, 415, 330, 20,6);
			g.setAntiAlias(false);

			g.setColor(new Color(80,120,220));
			g.fillRoundRect(224+2, 418, (330-4)*percent, 14, 4);
		}

		

		g.setFont(Multitris.FONT2);
		g.setColor(Color.white);
		g.drawString("Ping: "+ping/1000000+"ms",10,30);

		disconnect.render(g, focused);
		if (mode == HOST) {
			start.render(g, focused);
		}
	}

	public void drawDashedLine(Graphics g, int x, int y, int w) {
		for(int i=0; i<w; i+=8) {
			g.drawLine(x+i,y,x+i+5,y);
		}
	}

	/**
	* Updates the LobbyState.
	*
	* @param delta The time passed since the last call to update.
	*/
	public void update(int delta) {
		chatBox.setBackgroundColor(Color.black);

		if(focused == Multitris.CHAT_HOST_CHATBOX) {
			chatBox.setFocus(true);
			chatBox.setBackgroundColor(new Color(0x222222));
		} else {
			chatBox.setFocus(false);
		}

		// Update all bots at this player
		for (Bot b : botList) {
			b.update(delta);
		}

		if (isConnected) {
			//String observeActions = null;

			String[] actions = Multitris.clientCommunication.getActions();
			for(int i=0; i<actions.length; i++) {
				String action = actions[i];
			//for (String action : actions) {
				if (action.startsWith("id ")) { // this client recieves its own player id
					String[] params = action.split(" ", 2);
					int id = Integer.parseInt(params[1]);
					ClientCommunication.localID = id;
					Multitris.clientCommunication.sendAction("name "+localName);
				} else if (action.startsWith("newclient ")) { // another client joins the server
					String[] params = action.split(" ", 7);
					int id = Integer.parseInt(params[1]);
					Color color = new Color(Float.parseFloat(params[2]), Float.parseFloat(params[3]), Float.parseFloat(params[4]));
					boolean obs = params[5].equals("1");
					String name = params[6];
					//playerNames.put(id, name);
					//playerColors.put(id, color);
					clientList.put(id, new Client(id, name, color, obs));

					//chatLog.add(name+" joined the game.");
					chatLog.add(new Tuple3(null, null, name + " has joined the game."));
				} else if (action.startsWith("chat ")) { // someone sent a message
					String[] params = action.split(" ", 3);
					if (params.length == 3) {
						int id = Integer.parseInt(params[1]);
						String msg = params[2];
						//String name = playerNames.get(id);
						Client client = clientList.get(id);
						//chatLog.add("<"+name+"> "+msg);
						chatLog.add(new Tuple3(client.getName(), client.getColor(), msg));
					}
				} else if (action.startsWith("info ")) { // someone changed their info
					String[] params = action.split(" ", 4);
					if (params.length == 4) {
						int id = Integer.parseInt(params[1]);
						boolean obs = params[2].equals("1");
						String name = params[3];
						Client client = clientList.get(id);
						if (!name.equals(client.getName())) {
							chatLog.add(new Tuple3(null, null, client.getName()+" has changed nick to "+name+"."));
						}
						if (obs != client.isObserving()) {
							if (obs) {
								chatLog.add(new Tuple3(null, null, client.getName()+" will be observing."));
							} else {
								chatLog.add(new Tuple3(null, null, client.getName()+" will be playing."));
							}
						}
						client.setName(name);
						client.setObserving(obs);
					}
				} else if (action.startsWith("pong")) { // the ping reply
					String[] params = action.split(" ", 2);
					if (params.length == 2) {
						ping = System.nanoTime()-Long.parseLong(params[1]);
					}
				} else if (action.startsWith("clientdisconnected")) { // another client have disconnected
					String[] params = action.split(" ", 2);
					if (params.length == 2) {
						int id = Integer.parseInt(params[1]);
						//String name = playerNames.get(id);
						Client client = clientList.get(id);
						//chatLog.add(name + " has left the game");
						chatLog.add(new Tuple3(null, null, client.getName() + " has left the game."));
						//playerNames.remove(id);
						//playerColors.remove(id);

						clientList.remove(id); // not needed imo
					}
				} else if (action.startsWith("servershutdown")) { // the server shuts down
					//chatLog.add("The server have been shut down!");
					chatLog.add(new Tuple3(null, null, "The server have been shut down!"));
					//playerNames.clear(); // not needed to clear this at all
				} else if (action.startsWith("gamestarted ")) { // the game starts
					chatBox.setFocus(false);
					String[] params = action.split(" ", 2);
					if (params.length == 2) {
						int seed = Integer.parseInt(params[1]);

						ArrayList<Object> par = new ArrayList<Object>();
						par.add(clientList);
						par.add(seed);
						par.add(new Boolean(mode == HOST));
						par.add(localGameName);
						par.add(botList);
						stateMan.switchState(ApplicationStateManager.GAME, par);

						if (mode == HOST) {
							// this is moved to GameServer instead
							//Multitris.clientCommunication.removeFromCentralServer();
							//Multitris.clientCommunication.setActiveOnCentralServer();
						}
					}
				} else if (action.startsWith("pulse")) { // a game pulse, ignore an amount of actions (which are only gameactions)
					String[] pulseStrings = action.split(" ",3);
					int pulseId = Integer.parseInt(pulseStrings[1]);
					int numActions = Integer.parseInt(pulseStrings[2]);

					ArrayList<String> gameActions = new ArrayList<String>();
					for (int j=0; j<numActions; j++) {
						gameActions.add(actions[i+1]);
						i++;
					}

					missedActions.add(new Tuple2(pulseId, gameActions.toArray(new String[0])));
				} else if (action.startsWith("statestart")) {
					String[] pulseStrings = action.split(" ",2);
					
					wholeStateLength = Integer.parseInt(pulseStrings[1]);
				} else if (action.startsWith("statepart")) {
					// A part of the state was sent.
					String[] pulseStrings = action.split(" ",2);

					currentState.append(pulseStrings[1]);

					int percent = 0;
					if (wholeStateLength > 0) {
						percent = (100 * currentState.length()) / wholeStateLength;
					} else {
						percent = -1;
					}

					//chatLog.add(new Tuple3(null, null, "Recieving state - "+percent+"%"));
					
				} else if (action.startsWith("statefinished")) {
					// The state have been completely transfered.
					chatBox.setFocus(false);

					ArrayList<Object> par = new ArrayList<Object>();
					par.add(clientList);
					par.add(missedActions); // actions
					par.add(currentState.toString()); // serialized gamestate
					stateMan.switchState(ApplicationStateManager.GAME, par);
				} else if (action.startsWith("state")) {
					// THIS IS NOT USED ANYMORE
					//observeActions = action;
					System.out.println("Local ID: "+Multitris.clientCommunication.localID);
					String[] pulseStrings = action.split(" ",2);

					GameLogic gl = GameLogic.fromSerialization(null, new ArrayList<Tuple2<Integer, String[]>>(), pulseStrings[1]);

					if (gl == null) {
						chatLog.add(new Tuple3(null, null, "Unknown version! Can't load game."));
					} else {
						chatBox.setFocus(false);

						ArrayList<Object> par = new ArrayList<Object>();
						par.add(clientList);
						par.add(missedActions); // actions
						par.add(pulseStrings[1]); // serialized gamestate
						stateMan.switchState(ApplicationStateManager.GAME, par);
					}
				} else {
					chatLog.add(new Tuple3(null, null, "[unknown action] - "+action));
				}
				// needs to be implemented
			}

			if ((lastPing+=delta) > 2000) {
				lastPing = 0;
				Multitris.clientCommunication.sendAction("ping " + System.nanoTime());
			}
			if ((lastUpdate+=delta) > 20000 && mode == HOST) {
				lastUpdate = 0;
				Multitris.clientCommunication.refreshAddedServer();
				System.out.println("Trying to refresh central server");
			}

			/*if (observeActions != null && clientList.get(Multitris.clientCommunication.localID).isObserving()) {
				String[] pulseStrings = observeActions.split(" ",2);

				chatBox.setFocus(false);

				ArrayList<Object> par = new ArrayList<Object>();
				par.add(clientList);
				par.add(missedActions); // actions
				par.add(pulseStrings[1]); // serialized gamestate
				stateMan.switchState(ApplicationStateManager.GAME, par);
			}*/
		} else if (isConnecting == false) {
			LinkedList<String[]> servers = Multitris.clientCommunication.getServerList();
			if (servers != null) {
				for (String[] server : servers) {
					if (server.length == 3 || server[3].equals(localGameName)) {
						serverInfo = server;
						chatLog.add(new Tuple3(null, null, "Game info received, trying to connect using global address"));
						isConnecting = true;
						connectingTo = 1;
						Multitris.clientCommunication.connectToGameServer(serverInfo[0], 5398);
						gameServerAddress = serverInfo[0];
						/*if (!Multitris.clientCommunication.connectToGameServer(server[0], 5398)) {
							chatLog.add("Failed, trying to connect using local address");
							if (!Multitris.clientCommunication.connectToGameServer(server[1], 5398)) {
								chatLog.add("Failed. Couldn't connect to that game server.");
							} else {
								isConnected = true;
							}
						} else {
							isConnected = true;
						}
						if (isConnected) {
							chatLog.add("Connected");
							isConnected = true;
						}*/
						break;
					}
				}
				if (servers.size() == 0) {
					chatLog.add(new Tuple3(null, null, "Game doesn't exist"));
				}
			}
		} else if (isConnecting == true) {
			if (Multitris.clientCommunication.isConnectedToGameServer()) {
				isConnected = true;
				isConnecting = false;
				chatLog.add(new Tuple3(null, null, "Connected to game server"));
			} else if (!Multitris.clientCommunication.isConnectingToGameServer()) {
				if (connectingTo == 1) {
					connectingTo = 2;
					chatLog.add(new Tuple3(null, null, "Trying to connect using local address"));
					Multitris.clientCommunication.connectToGameServer(serverInfo[1], 5398);
					gameServerAddress = serverInfo[1];
				} else if (connectingTo == 2) {
					isConnecting = false;
					isConnected = false;
					chatLog.add(new Tuple3(null, null, "Failed to connect to server"));
				}
			}
		}
	}

	/**
	* Sets a mode for the state.
	*
	* @param parameter the mode to set.
	*/
	public void setMode(Object parameter) {
		System.out.println("Set mode");
		ArrayList params = (ArrayList) parameter;
		int lastMode = mode;
		mode = (Integer)params.get(0);

		wholeStateLength = 0;
		if (mode == REMATCH) {
			// Reset changed states
			clientList = (HashMap<Integer, Client>)params.get(1);
			Iterator<Client> clients = clientList.values().iterator();
			while (clients.hasNext()) {
				Client c = clients.next();
				if (c.isDisconnected()) {
					clients.remove();
				}
			}

			botList = (ArrayList<Bot>)params.get(2);
			focused = Multitris.CHAT_HOST_CHATBOX;
			selected = 0;
			scroll = 0;

			chatBox.setText("");
			
			currentState = new StringBuilder();

			mode = lastMode;

			joinAsObserver.setState(clientList.get(Multitris.clientCommunication.localID).isObserving());
		} else {
			chatLog.clear();
			clientList.clear();
			botList.clear();
			isConnected = false;

			chatBox.setText("");

			currentState = new StringBuilder();

			if (mode == HOST) {
				joinAsObserver.setState(false);
				localName = (String)params.get(1);
				localGameName = (String)params.get(2);
				int numPlayers = (Integer)params.get(3);
				boolean privateGame = (Boolean)params.get(4);
				boolean allowObservers = (Boolean)params.get(5);
				Multitris.gameServer = new GameServer(numPlayers, 1000/Multitris.PULSES_PER_SECOND, 5398, allowObservers);
				System.out.println("Connecting to local game server");
				chatLog.add(new Tuple3(null, null, "Connecting to local game server"));
				Multitris.clientCommunication.connectToGameServer("localhost", 5398);
				chatLog.add(new Tuple3(null, null, "Notifying central server"));
				Multitris.clientCommunication.addToServerList(numPlayers, privateGame, localGameName);
				chatLog.add(new Tuple3(null, null, "Server up and running"));
				isConnected = true;

			} else {
				joinAsObserver.setState(false);
				localName = (String)params.get(1);
				localGameName = (String)params.get(2);
				Multitris.clientCommunication.requestServerInformation(localGameName);
				System.out.println("Requesting address for game '"+localGameName+"' from the central server");

				chatLog.add(new Tuple3(null, null, "Requesting game info"));
			}
			// When this state is switched to, reset the focus.
			focused = Multitris.CHAT_HOST_CHATBOX;
			selected = 0;
			scroll = 0;
		}
	}

	/**
	* Focuses the given LobbyTextField;
	*
	* @param focusedField The LobbyTextField to focus.
	*/
	public void focusField(LobbyTextField tf) {
		if (tf.equals(chatBox)) {
			focused = Multitris.CHAT_HOST_CHATBOX;
		}
	}
	/**
	* Focuses the given Checkbox;
	*
	* @param c The Checkbox to focus.
	*/
	public void focusBox(Checkbox c) {
		if(c.equals(joinAsObserver)) {
			focused = Multitris.CHAT_HOST_JOINASOBSERVER;
		}
	}

	/**
	* Informs the LobbyState that an action has been requested.
	*
	* @param action The action requested.
	*/
	public void doInputAction(String action) {

		if(action.equals("p_shift")) {
			shift = true;
		}
		if(action.equals("r_shift")) {
			shift = false;
		}

		if(mode == HOST) {
			if(action.equals("p_up") || (action.equals("p_tab") && shift)) {
				focused--;
				if(focused < Multitris.CHAT_HOST_CHATBOX)
					focused = Multitris.CHAT_HOST_LEAVE;
			}
			if(action.equals("p_down") || (action.equals("p_tab") && !shift)) {
				focused++;
				if(focused > Multitris.CHAT_HOST_LEAVE)
					focused = Multitris.CHAT_HOST_CHATBOX;
			}
		}
		if(mode == JOIN) {
			if(action.equals("p_up") || (action.equals("p_tab") && shift)) {
				focused--;
				if(focused == Multitris.CHAT_HOST_START)
					focused = Multitris.CHAT_JOIN_JOINASOBSERVER;
				if(focused < Multitris.CHAT_JOIN_CHATBOX)
					focused = Multitris.CHAT_JOIN_DISCONNECT;
			}
			if(action.equals("p_down") || (action.equals("p_tab") && !shift)) {
				focused++;
				if(focused == Multitris.CHAT_HOST_START)
					focused = Multitris.CHAT_JOIN_DISCONNECT;
				if(focused > Multitris.CHAT_JOIN_DISCONNECT)
					focused = Multitris.CHAT_HOST_CHATBOX;
			}
		}

		if(action.startsWith("p_use")) {
			activateFocused();
		}
		if(action.equals("p_cancel")) {
			if(mode == HOST) {
				focused = Multitris.CHAT_HOST_LEAVE;
			}
			if(mode == JOIN) {
				focused = Multitris.CHAT_JOIN_DISCONNECT;
			}
			activateFocused();
		}
	}

	private void activateFocused() {
		if(mode == HOST) {
			if(focused == Multitris.CHAT_HOST_START) {
				Multitris.clientCommunication.sendAction("start");
			}
		}
		if(focused == Multitris.CHAT_HOST_LEAVE) { // leave button for both host and join
			if (mode == HOST) {
				Multitris.clientCommunication.removeFromCentralServer();
			}

			chatBox.setFocus(false);
			Multitris.clientCommunication.disconnectFromGameServer();
			stateMan.switchState(ApplicationStateManager.MENU);
		}
		if(focused == Multitris.CHAT_HOST_JOINASOBSERVER) {
			joinAsObserver.switchState();
			Multitris.clientCommunication.sendAction("info "+(joinAsObserver.getState() ? "1" : "0")+" "+localName);
		}
	}

	public void componentActivated(AbstractComponent object) {
		if (object == chatBox) {
			String str = chatBox.getText();
			chatBox.setText("");
			if (str.startsWith("/add")) { // Adds a computer controlled player, hosted by the local player
				botList.add(new Bot(gameServerAddress));
				chatLog.add(new Tuple3(null, null, "Computer player added"));
			} else if (str.startsWith("/remove")) { // Removes a computer controlled player hosted by the local player
				if (botList.size() > 0) {
					Bot b = botList.get(botList.size()-1);
					b.remove();
					botList.remove(b);
					chatLog.add(new Tuple3(null, null, "Computer player removed"));
				} else {
					chatLog.add(new Tuple3(null, null, "No computer players are hosted by you"));
				}
			} else if (str.startsWith("/nick ")) { // Request a nick change
				String name = str.substring(6);
				localName = name;
				Multitris.clientCommunication.sendAction("info "+(joinAsObserver.getState() ? "1" : "0")+" "+localName);
			} else { // Just send a chat message
				Multitris.clientCommunication.sendAction("chat "+str);
			}
		}
	}
}